package com.dongyu.tinymap;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Point;
import ohos.app.Context;
import ohos.app.dispatcher.task.TaskPriority;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;

public class TinyMap extends Component implements Component.DrawTask, Component.TouchEventListener{


    // 地图来源
    public enum MapSource {
        GAODE_VECTOR, GAODE_ROAD, GAODE_SATELLITE, GOOGLE_VECTOR, GOOGLE_SATELLITE
    }
    // HiLogLabel
    private static final HiLogLabel sLogLabel = new HiLogLabel(HiLog.LOG_APP, 0x00101, "TinyMap");
    // 高德地图 - 矢量
    private static final String GAODE_V_MAP_URL = "https://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=2&style=8&x=%d&y=%d&z=%d";
    // 高德地图 - 道路
    private static final String GAODE_R_MAP_URL = "https://webst02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=2&style=8&x=%d&y=%d&z=%d";
    // 高德地图 - 卫星
    private static final String GAODE_S_MAP_URL = "https://webst01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=2&style=6&x=%d&y=%d&z=%d";
    // 谷歌地图 - 矢量
    @Deprecated
    private static final String GOOGLE_V_MAP_URL = "https://mt2.google.cn/vt/lyrs=m&scale=2&hl=zh-CN&gl=cn&x=%d&y=%d&z=%d";
    // 谷歌地图 - 卫星
    @Deprecated
    private static final String GOOGLE_S_MAP_URL = "https://mt2.google.cn/vt/lyrs=s&scale=2&hl=zh-CN&gl=cn&x=%d&y=%d&z=%d";

    // 切片图片长度
    private static final int TILE_LENGTH = 512;
    // 墨卡托投影总半轴长度
    private static final double OVER_LENGTH = 20037508.3427892;
    // 组件中央坐标
    private Point mCenterPoint = new Point(12956517.35f,4864667.87f);
    // LOD
    private int mLOD = 15;
    // 某LOD下的行数（列数）
    private int mRowCount;
    // 某LOD下单个切片在X坐标（Y坐标）上所代表地图距离的长度
    private double mTileRealLength;
    // 四至范围
    private double mXMin, mXMax, mYMin, mYMax;
    // 切片范围
    private int mRowMin, mRowMax, mColMin, mColMax;

    // 用户平移地图时第一次按下的位置（组件坐标）
    private float touchedDownX;
    private float touchedDownY;

    // 组件的宽度和高度（原为虚拟坐标vp，也可以设置为px）
    private double virtualWidth, virtualHeight;

    // 地图切片
    private ArrayList<Tile> tiles;
    // 地图元素
    private ArrayList<Element> elements;

    // 地图底图源
    private MapSource mapSource = MapSource.GAODE_SATELLITE;

    /**
     * 获取地图底图源
     * @return MapSource
     */
    public MapSource getMapSource() {
        return mapSource;
    }

    /**
     * 设置地图底图源
     * @param mapSource
     */
    public void setMapSource(MapSource mapSource) {
        this.mapSource = mapSource;
    }

    /**
     * 构造方法，如果需要在xml中定义，一定要复写带attrSet的构造方法
     * @param context
     */
    public TinyMap(Context context) {
        super(context);
        initMapCanvas(false);
    }

    public TinyMap(Context context, AttrSet attrSet) {
        super(context, attrSet);
        initMapCanvas(false);
    }

    public TinyMap(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        initMapCanvas(false);
    }

    public TinyMap(Context context, AttrSet attrSet, int resId) {
        super(context, attrSet, resId);
        initMapCanvas(false);
    }

    /**
     * 放大
     */
    public void zoomIn(){
        mLOD = mLOD + 1;
        initMapCanvas(false);

        // 放大后，需要将每一个地图元素的屏幕坐标进行重建，下同
        for (Element element : elements) {
            double onComponent_x = (element.getMercatorPoint().getPointX() - mCenterPoint.getPointX()) / mTileRealLength * TILE_LENGTH + virtualWidth / 2;
            double onComponent_y = (mCenterPoint.getPointY() - element.getMercatorPoint().getPointY()) / mTileRealLength * TILE_LENGTH + virtualHeight / 2;
            element.setNowPoint(new Point((float)onComponent_x, (float)onComponent_y));
            element.setOriginPoint(new Point((float)onComponent_x, (float)onComponent_y));
        }

    }

    /**
     * 缩小
     */
    public void zoomOut() {
        mLOD = mLOD - 1;
        initMapCanvas(false);

        for (Element element : elements) {
            double onComponent_x = (element.getMercatorPoint().getPointX() - mCenterPoint.getPointX()) / mTileRealLength * TILE_LENGTH + virtualWidth / 2;
            double onComponent_y = (mCenterPoint.getPointY() - element.getMercatorPoint().getPointY()) / mTileRealLength * TILE_LENGTH + virtualHeight / 2;
            element.setNowPoint(new Point((float)onComponent_x, (float)onComponent_y));
            element.setOriginPoint(new Point((float)onComponent_x, (float)onComponent_y));
        }
    }

    /**
     * 刷新地图
     */
    public void refreshMap(){
        initMapCanvas(true);
    }

    /**
     * 增加地图元素
     * @param x
     * @param y
     * @param resourceId
     */
    public void addElement(float x, float y, int resourceId) {
        if (elements == null) {
            elements = new ArrayList<>();
        }

        try {
            InputStream drawableInputStream = getContext().getResourceManager().getResource(resourceId);
            ImageSource imageSource = ImageSource.create(drawableInputStream, new ImageSource.SourceOptions());
            ImageSource.DecodingOptions options = new ImageSource.DecodingOptions();
            options.desiredSize = new Size(100, 100);
            PixelMap pixelMap = imageSource.createPixelmap(options);

            if (pixelMap != null) {

                double onComponent_x = (x - mCenterPoint.getPointX()) / mTileRealLength * TILE_LENGTH + virtualWidth / 2;
                double onComponent_y = (mCenterPoint.getPointY() - y) / mTileRealLength * TILE_LENGTH + virtualHeight / 2;

                Element element = new Element(pixelMap, (float) onComponent_x, (float) onComponent_y);
                element.setMercatorPoint(new Point(x, y));
                elements.add(element);
                invalidate();
            }
        }catch (Exception e) {
            log(e.toString());
        }
    }

    /**
     * 初始化地图画布
     * @param refresh
     */
    private void initMapCanvas(boolean refresh) {

        mRowCount = (int)Math.pow(2, mLOD);
        // log("mRowCount:" + mRowCount);
        mTileRealLength = OVER_LENGTH * 2 / mRowCount;
        // log("mTileRealLength:" + mTileRealLength);

        // log("Width : " + getWidth());
        // log("Height : " + getHeight());
        // log("screenDensitylevel : " + getContext().getResourceManager().getDeviceCapability().screenDensity / 160);
        // int screenDensitylevel = getContext().getResourceManager().getDeviceCapability().screenDensity / 160;
        // virtualWidth = getWidth() / screenDensitylevel;
        // virtualHeight = getHeight() / screenDensitylevel;
        virtualWidth = getWidth();
        virtualHeight = getHeight();

        mXMin = mCenterPoint.getPointX() - virtualWidth / 2.0 * mTileRealLength / TILE_LENGTH;
        mXMax = mCenterPoint.getPointX() + virtualWidth / 2.0 * mTileRealLength / TILE_LENGTH;
        mYMin = mCenterPoint.getPointY() - virtualHeight / 2.0 * mTileRealLength / TILE_LENGTH;
        mYMax = mCenterPoint.getPointY() + virtualHeight / 2.0 * mTileRealLength / TILE_LENGTH;

        /*
        log("mXMin:" + mXMin);
        log("mXMax:" + mXMax);
        log("mYMin:" + mYMin);
        log("mYMax:" + mYMax);
         */
        mColMin = (int)Math.floor((mXMin + OVER_LENGTH) / mTileRealLength);
        mColMax = Math.min((int)Math.floor((mXMax + OVER_LENGTH) / mTileRealLength), mRowCount - 1);
        mRowMin = Math.min(mRowCount - 1 - (int)Math.floor((mYMax + OVER_LENGTH) / mTileRealLength), mRowCount - 1);
        mRowMax = mRowCount - 1 - (int)Math.floor((mYMin + OVER_LENGTH) / mTileRealLength);

        /*
        log("mColMin:" + mColMin);
        log("mColMax:" + mColMax);
        log("mRowMin:" + mRowMin);
        log("mRowMax:" + mRowMax);
         */
        this.addDrawTask(this);
        this.setTouchEventListener(this);

        initTiles(refresh);
        initElement(refresh);
    }

    /**
     * 初始化地图切片
     * @param refresh
     */
    private void initTiles(boolean refresh){

        if (tiles == null || refresh == true) {
            tiles = new ArrayList<>();
        }
        tiles.removeIf(tile -> !tile.isInBoundary(mRowMin, mRowMax, mColMin, mColMax));

        getContext().getGlobalTaskDispatcher(TaskPriority.DEFAULT).asyncDispatch(new Runnable() {
            @Override
            public void run() {
                for (int row = mRowMin; row <= mRowMax; row ++) {
                    for (int col = mColMin; col <= mColMax; col ++) {
                        boolean hasThisTile = false;
                        for (Tile tile : tiles) {
                            if (tile.getRow() == row && tile.getCol() == col) {
                                hasThisTile = true;
                            }
                        }
                        if (hasThisTile) {
                            getContext().getUITaskDispatcher().asyncDispatch(new Runnable() {
                                @Override
                                public void run() {
                                    TinyMap.this.invalidate();
                                }
                            });
                            continue;
                        }

                        String urlString;
                        switch (mapSource) {
                            case GAODE_ROAD:
                                urlString = String.format(GAODE_R_MAP_URL, col, row, mLOD);
                                break;
                            case GAODE_SATELLITE:
                                urlString = String.format(GAODE_S_MAP_URL, col, row, mLOD);
                                break;
                            case GAODE_VECTOR:
                                urlString = String.format(GAODE_V_MAP_URL, col, row, mLOD);
                                break;
                            case GOOGLE_VECTOR:
                                urlString = String.format(GOOGLE_V_MAP_URL, col, row, mLOD);
                                break;
                            case GOOGLE_SATELLITE:
                                urlString = String.format(GOOGLE_S_MAP_URL, col, row, mLOD);
                                break;
                            default:
                                urlString = String.format(GOOGLE_S_MAP_URL, col, row, mLOD);
                                break;

                        }
                        // log("urlString:" + urlString);
                        PixelMap pixelMap = getImagePixmap(urlString);
                        // log("pixelMap Width:" + pixelMap.getImageInfo().size.width);
                        // log("pixelMap Height:" + pixelMap.getImageInfo().size.height);
                        if (pixelMap == null) {
                            continue;
                        }
                        double tile_x = col * mTileRealLength - OVER_LENGTH;
                        double tile_y = OVER_LENGTH - row * mTileRealLength;
                        double onComponent_x = (tile_x - mCenterPoint.getPointX()) / mTileRealLength * TILE_LENGTH + virtualWidth / 2;
                        double onComponent_y = (mCenterPoint.getPointY() - tile_y) / mTileRealLength * TILE_LENGTH + virtualHeight / 2;

                        /*
                        log("tile_x:" + tile_x);
                        log("tile_y:" + tile_y);
                        log("onComponent_x:" + onComponent_x);
                        log("onComponent_y:" + onComponent_y);
                         */
                        Tile tile = new Tile(pixelMap, (float) onComponent_x,(float)onComponent_y);
                        tile.setCol(col);
                        tile.setRow(row);
                        tiles.add(tile);
                        getContext().getUITaskDispatcher().asyncDispatch(new Runnable() {
                            @Override
                            public void run() {
                                TinyMap.this.invalidate();
                            }
                        });
                    }
                }
            }
        });
    }

    /**
     * 初始化地图元素
     * @param refresh
     */
    private void initElement(boolean refresh) {
        if (elements == null || refresh == true) {
            elements = new ArrayList<>();
        }
    }

    /**
     * 绘制地图画布
     * @param component
     * @param canvas
     */
    @Override
    public void onDraw(Component component, Canvas canvas) {
        // 绘制地图切片
        for (int i = 0; i < tiles.size(); i ++) {
            Tile tile = tiles.get(i);
            canvas.drawPixelMapHolder(tile, tile.getX(), tile.getY(), new Paint());
        }
        // 绘制地图元素
        for (int i = 0; i < elements.size(); i ++) {
            Element element = elements.get(i);
            canvas.drawPixelMapHolder(element, element.getX(), element.getY(), new Paint());
        }

    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {

        MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());

        // 按下
        if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
            touchedDownX = point.getX() - component.getContentPositionX();
            touchedDownY = point.getY() - component.getContentPositionY();
            return true;
        }
        // 拖动
        if (touchEvent.getAction() == TouchEvent.POINT_MOVE)
        {
            for (int i = 0; i < tiles.size(); i ++) {
                Tile tile = tiles.get(i);
                tile.setX((point.getX() - component.getContentPositionX()) - touchedDownX + tile.getOriginX());
                tile.setY((point.getY() - component.getContentPositionY()) - touchedDownY + tile.getOriginY());
            }
            for (int i = 0; i < elements.size(); i ++) {
                Element element = elements.get(i);
                element.setX((point.getX() - component.getContentPositionX()) - touchedDownX + element.getOriginX());
                element.setY((point.getY() - component.getContentPositionY()) - touchedDownY + element.getOriginY());
            }
            component.invalidate();
        }
        // 松开
        if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {
            float newCenterPointX = (float) (mCenterPoint.getPointX() - (point.getX() - component.getContentPositionX() - touchedDownX) / TILE_LENGTH * mTileRealLength);
            float newCenterPointY = (float) (mCenterPoint.getPointY() + (point.getY() - component.getContentPositionY() - touchedDownY) / TILE_LENGTH * mTileRealLength);
            mCenterPoint = new Point(newCenterPointX, newCenterPointY);

            for (int i = 0; i < tiles.size(); i ++) {
                Tile tile = tiles.get(i);
                tile.setX((point.getX() - component.getContentPositionX()) - touchedDownX + tile.getOriginX());
                tile.setY((point.getY() - component.getContentPositionY()) - touchedDownY + tile.getOriginY());
                tile.setOriginX(tile.getX());
                tile.setOriginY(tile.getY());
            }
            for (int i = 0; i < elements.size(); i ++) {
                Element element = elements.get(i);
                element.setX((point.getX() - component.getContentPositionX()) - touchedDownX + element.getOriginX());
                element.setY((point.getY() - component.getContentPositionY()) - touchedDownY + element.getOriginY());
                element.setOriginX(element.getX());
                element.setOriginY(element.getY());
            }
            initMapCanvas(false);
        }
        return false;
    }

    /**
     * 打印信息
     * @param output
     */
    private static void log(String output) {
        HiLog.info(sLogLabel, output);
    }


    /**
     * 获取网络中的ImagePixmap
     * @param urlString
     * @return
     */
    public static PixelMap getImagePixmap(String urlString) {
        try {
            URL url = new URL(urlString);
            URLConnection con = url.openConnection();
            con.setConnectTimeout(5*1000);
            InputStream is = con.getInputStream();
            ImageSource source = ImageSource.create(is, new ImageSource.SourceOptions());
            ImageSource.DecodingOptions options = new ImageSource.DecodingOptions();
            options.desiredSize = new Size(513,513);
            PixelMap pixelMap = source.createPixelmap(options);
            is.close();
            return pixelMap;
        } catch (Exception e) {
            log(e.toString());
            return null;
        }
    }
}
